# _*_ coding: utf-8 _*_
import numpy as np
from numpy.linalg import norm
from copy import deepcopy
from time import time
from time import strftime
from time import gmtime
from math import exp
from math import floor
from random import uniform
from random import sample
from random import randint
from random import shuffle
from functools import wraps
import matplotlib.pyplot as plt


class Lifecycle:
    """
        Lifecycle of an optimizer running progress
    """

    def __init__(self, iterations=None, evaluations=None, deadline=None):
        self.iterations = int(1e9) if iterations is None else iterations
        self.evaluations = int(1e9) if evaluations is None else evaluations
        self.deadline = int(1e9) if deadline is None else deadline * 60  # minutes
        self.it = 0
        self.eva = 0
        self.__start_time = time()
        self.elapsed = time() - self.__start_time
        self.__pre = self.elapsed
        self.status = 'mounted'
        self.message = None
        self.gBest = []
        self.data = None

    def set_stopping_condition(self, iterations=None, evaluations=None, deadline=None):
        self.iterations = int(1e9) if iterations is None else iterations
        self.evaluations = int(1e9) if evaluations is None else evaluations
        self.deadline = int(1e9) if deadline is None else deadline * 60  # minutes

    def update(self, it=None, eva=None, timing=True, gBest=None, data=None):
        if it is not None:
            self.it += it
        if eva is not None:
            self.eva += eva
        if timing:
            self.elapsed = time() - self.__start_time
        if gBest is not None:
            self.gBest.append(gBest)
        self.data = data
        self.status = 'alive'

    def check_stopping_condition(self, mute=True):
        msg, stop = ['', '', ''], False
        if self.it >= self.iterations:
            msg[0] = 'iterations limitation reached. '
            stop = True
        if self.eva >= self.evaluations:
            msg[1] = 'evaluations limitation reached. '
            stop = True
        if self.elapsed >= self.deadline:
            msg[2] = 'deadline reached. '
            stop = True
        if stop:
            self.message = msg[0] + msg[1] + msg[2]
            self.status = 'closed'
            t = strftime('%H:%M:%S', gmtime(self.elapsed))
            if mute is False:
                print(f'Terminated at {self.it}, time cost {t}s')

        return stop

    def report(self, per_sec=True, graph=True, mapping=None):
        now = round(self.elapsed)
        if now != self.__pre or not per_sec:
            t = strftime('%H:%M:%S', gmtime(self.elapsed))
            print(f'{t} at {self.it:5d} route length {self.data[-1]:.2f}')
            self.__pre = now
            if graph:
                if mapping is not None:
                    plt.clf()
                    plt.subplot(1, 2, 1)
                    plt.plot(range(len(self.gBest)), self.gBest, linestyle='-', label='gBest')
                    plt.title(label=f'iterations {self.it}, d: {self.gBest[-1]:.2f}')
                    ax = plt.gca()
                    ax.spines['top'].set_color('none')
                    ax.spines['right'].set_color('none')
                    plt.legend()
                    plt.subplot(1, 2, 2)
                    route = mapping[0]
                    draw = mapping[1]
                    draw(plt, route, dot_size=20, show_text=True)
                    ax = plt.gca()
                    # ax.spines['top'].set_color('none')
                    # ax.spines['right'].set_color('none')
                    # ax.spines['left'].set_color('none')
                    # ax.spines['bottom'].set_color('none')
                    ax.set_aspect('equal')
                    plt.pause(0.001)
                else:
                    plt.clf()
                    plt.plot(range(len(self.gBest)), self.gBest, linestyle='-', label='gBest')
                    plt.title(label=f'iterations {self.it}, d: {self.gBest[-1]:.2f}')
                    plt.pause(0.001)


def greedy_route(data, depot=None):
    data


